//  @ Project : GagalPanen
//  @ File Name : game.cpp
//  @ Date : 3/9/2011
//  @ Author : 
//
//

#include <cstdlib>
#include <vector>
#include <string>
#include <assert.h>
#include "fruit.hpp"
#include "game.hpp"
#include "gameconstants.hpp"
#include "shopconstants.hpp"
#include "fieldconstants.hpp"
#include "houseconstants.hpp"
#include "cell.hpp"
#include "direction.hpp"
#include "plant.hpp"
#include "seedconstants.hpp"
#include "season.hpp"
#include "plantconstants.hpp"
#include <fstream>
#include <iostream>

// maps must be initialized before creatures!
Game::Game(std::string username, std::string filename):
    maps_(this), creatures_(this), inventory_(this), time_(),
        disaster_(this),
        view_(this), parser_(this), username_(username),
        filename_(filename), exit_called_(false) {

}

// maps must be initialized before creatures!
Game::Game(std::ifstream& is, std::string filename) :
    maps_(this, is), creatures_(this, is), inventory_(this, is), time_(is),
        disaster_(this, is),
        view_(this), parser_(this), username_(), filename_(filename),
        exit_called_(false) {

  char buffer[1500];
  is.getline(buffer, 1499);
  return;
}

Game::~Game() {}

void Game::playerSaveGame() {
  std::ofstream os(filename_.c_str());
  // opened automatically

  getMaps()->save(os);
  getCreatures()->save(os);
  getInventory()->save(os);
  getGameTime()->save(os);

  os << username_ << std::endl;

  os.close();

  view_.gameSaved(filename_);

  return;
}

int Game::getActionsCount() {
  return time_.getDayTime();
}

Creatures* Game::getCreatures() {
  return &creatures_;
}

Inventory* Game::getInventory() {
  return &inventory_;
}

GameMaps* Game::getMaps() {
  return &maps_;
}

GameTime* Game::getGameTime() {
  return &time_;
}

View* Game::getView() {
  return &view_;
}

Disaster* Game::getDisaster() {
  return &disaster_;
}

int Game::getMaximumActionsPerDay() {
  return GameConstants::getMaximumActionsPerDay();
}

void Game::playerBuy(std::string item_name, int count) {

  GridMap* location = getCreatures()->getPlayer()->getMap();
  if (location != getMaps()->getShop()) {
    view_.needShopArea();
    return;
  }

  if (count <= 0) {
    view_.invalidCount();
    return;
  }

  int item_id = -1;
  for (int i = 0; i < (int)SeedConstants::getSeedsCount(); ++i) {
    if (SeedConstants::getSeedName(i) == item_name) {
      item_id = i;
      break;
    }
  }

  if (item_id == -1) {
    view_.noSuchSeed();
    return;
  }
  if ((long long)SeedConstants::getSeedBuyPrice(item_id) * count >
          getInventory()->getMoney()) {
    view_.notEnoughMoney();
  } else {
    for (int j = 0; j < count; ++j) {
      Item* new_seed = new Seed(this, item_id);
      if (getInventory()->canAdd(new_seed)) {
        getInventory()->modifyMoney(-SeedConstants::getSeedBuyPrice(item_id));
        getInventory()->add(new_seed);
        continue;
      } else {
        view_.inventoryFull();
        delete new_seed;
        if (j) {
          view_.itemPurchased(item_name, j, j * SeedConstants::getSeedBuyPrice(item_id));
        }
        return;
      }
    }
    view_.itemPurchased(item_name, count, count * SeedConstants::getSeedBuyPrice(item_id));
  }
  view_.showMoney();
  advanceTime();
  return;
}

void Game::playerSell(int slot_number, int count) {
  if (slot_number <= 0 || slot_number > getInventory()->getFilledSlotCount()) {
    view_.invalidItemSlot();
    return;
  }
  if (count <= 0) {
    view_.invalidCount();
    return;
  }
  if (getInventory()->numberOfItemsAtSlot(slot_number) < count) {
    view_.triedToSellTooManyItems();
    return;
  } else {
    Item* item = getInventory()->getItemAt(slot_number);
    std::string item_name = item->getName();
    int total_money = 0;
    for (int i = 0; i < count; ++i) {
      Item* item = getInventory()->getItemAt(slot_number);
      getInventory()->modifyMoney(item->getPrice());
      total_money += item->getPrice();
      getInventory()->removeItem(item);
    }
    view_.itemSold(item_name, count, total_money);
  }
  view_.showMoney();
  advanceTime();
  return;
}

void Game::playerEat(int slot_number) {
  if (slot_number <= 0 || slot_number > getInventory()->getFilledSlotCount()) {
    view_.invalidItemSlot();
    return;
  }
  Item* item = getInventory()->getItemAt(slot_number);
  if (item->getType() != kFruit) {
    view_.itemNotEdible();
    return;
  } else {
    Fruit* fruit = (Fruit*) item;
    view_.eatSuccess(fruit->getName());
    fruit->eat();
    getInventory()->removeItem(fruit);
  }
  advanceTime();
}

void Game::playerHarvest() {
  Cell* player_cell = getCreatures()->getPlayer()->getCell(); // get player's cell
  Direction player_dir = getCreatures()->getPlayer()->getDirection(); // get player's direction
  Cell* in_front_of_player = getMaps()->getCellInFrontOff(player_cell, // get in front of cell
          player_dir);
  if (!getCreatures()->getPlants()->containsPlantAt(in_front_of_player)) {
    // no plant present
    view_.harvestNothing();
  } else {
    Plant* plant = getCreatures()->getPlants()->getPlantAt(in_front_of_player);
    if (!plant->isHarvestable()) {
      // plant not ready to be harvested
      view_.harvestNothing();
    } else {
      // do harvest
      Fruit* result = plant->harvest();
      view_.harvestSuccess(result->getName(), 1);
      // add to inventory
      if (getInventory()->canAdd(result)) {
        getInventory()->add(result);
      } else {
        view_.inventoryFull();
      }
    }
  }
  // advance game time
  advanceTime();
}

void Game::playerRight() {
  getCreatures()->getPlayer()->turnRight();
  advanceTime();
}
void Game::playerLeft() {
  getCreatures()->getPlayer()->turnLeft();
  advanceTime();
}
void Game::playerUp(int step_count) {
  int day = getGameTime()->getDayCount();
  while (day == getGameTime()->getDayCount() && step_count) {
    if (!getCreatures()->getPlayer()->canMove()) {
      view_.moveObstructed();
      return;
    } else {
      getCreatures()->getPlayer()->move();
    }
    --step_count;
    advanceTime();
  }
}
void Game::playerDown() {
  getCreatures()->getPlayer()->turnLeft();
  getCreatures()->getPlayer()->turnLeft();
  advanceTime();
}

void Game::playerEast() {
  if (getCreatures()->getPlayer()->getDirection() != kRight) {
    getCreatures()->getPlayer()->setDirection(kRight);
    advanceTime();
  } else {
    playerUp(1);
  }
}
void Game::playerWest() {
  if (getCreatures()->getPlayer()->getDirection() != kLeft) {
    getCreatures()->getPlayer()->setDirection(kLeft);
    advanceTime();
  } else {
    playerUp(1);
  }
}
void Game::playerNorth() {
  if (getCreatures()->getPlayer()->getDirection() != kUp) {
    getCreatures()->getPlayer()->setDirection(kUp);
    advanceTime();
  } else {
    playerUp(1);
  }
}
void Game::playerSouth() {
  if (getCreatures()->getPlayer()->getDirection() != kDown) {
    getCreatures()->getPlayer()->setDirection(kDown);
    advanceTime();
  } else {
    playerUp(1);
  }
}

void Game::playerPlow() {//by Ami
  Cell* player_cell = getCreatures()->getPlayer()->getCell(); // get player's cell
  Direction player_dir = getCreatures()->getPlayer()->getDirection(); // get player's direction
  Cell* in_front_of_player = getMaps()->getCellInFrontOff(player_cell, // get in front of cell
          player_dir);
  if (!in_front_of_player->isPlantable()) {
    // not plantable
    view_.plowUnplantableCell(); //irv
  } else {
    PlantableCell* plow_target = (PlantableCell* )(in_front_of_player);
    plow_target->plow(); // irv
    view_.plowSuccess();
    /*
    if (!plow_target->isPlanted()) {
        if (in_front_of_player->isEnterable()) {
            if (!plow_target->isPlowed()) {
               plow_target->plow();
           } else {
                  //TODO
                  //has been plowed
                  }
        } else {
               Plant* plant = getCreatures()->getPlants()->getPlantAt(in_front_of_player);
                if (!plant->isMature()) {
                  plow_target->plow();
                } else {
                  // TO DO
                  // has mature plant
                }
               }
    } else {
        //TODO
        //not plantable
        } */
  }
  
  // advance game time
  advanceTime();
  return;
}

void Game::playerPut(int slot_id, int count) {

  if (slot_id <= 0 || slot_id > getInventory()->getFilledSlotCount()) {
    view_.invalidItemSlot();
    return;
  }
  if (count <= 0) {
    view_.invalidCount();
    return;
  }

  if (getInventory()->numberOfItemsAtSlot(slot_id) < count) {
    view_.triedToSellTooManyItems();
    return;
  }

  Cell* player_cell = getCreatures()->getPlayer()->getCell(); // get player's cell
  Direction player_dir = getCreatures()->getPlayer()->getDirection(); // get player's direction
  Cell* in_front_of_player = getMaps()->getCellInFrontOff(player_cell, // get in front of cell
          player_dir);
  
  if (getCreatures()->getDropBoxes()->containsDropBoxAt(in_front_of_player)) {
    // jual
    playerSell(slot_id, count);
    return;
  }

  if (!in_front_of_player->isPlantable() ||
          getInventory()->getItemAt(slot_id)->getType() != kSeed) {
    // discard
    view_.discard(getInventory()->getItemAt(slot_id)->getName(), count);
    for (int i = 0; i < count; ++i) {
      Item* item = getInventory()->getItemAt(slot_id);
      getInventory()->removeItem(item);
    }

  } else {

    Seed* seed = (Seed*)getInventory()->getItemAt(slot_id);
    getInventory()->removeItem(seed);
    
    for (int i = 0; i < count - 1; ++i) {
      Item* item = getInventory()->getItemAt(slot_id);
      getInventory()->removeItem(item);
    }

    PlantableCell* pc = (PlantableCell*) in_front_of_player;
    if (!pc->isPlowed() || getCreatures()->getPlants()->containsPlantAt(pc)) {
      // discard rest
      if (count) {
        view_.discard(seed->getName(), count);
      }
    } else {
      if (count-1) {
        view_.discard(seed->getName(), count-1);
      }
      Plant* newplant = new Plant(this, pc, seed->getPlantId());
      getCreatures()->getPlants()->addPlant(newplant);
      view_.plantSuccess(seed->getName());
    }
  }

  // advance game time
  advanceTime();
  return;
}

void Game::playerShowStore() {

  GridMap* location = getCreatures()->getPlayer()->getMap();
  if (location != getMaps()->getShop()) {
    view_.needShopArea();
    return;
  }

  std::vector<int> seeds;

  for (int i = 0; i < SeedConstants::getSeedsCount(); ++i) {
    seeds.push_back(i);
  }

  view_.listSeeds(seeds);
  return;
}

void Game::playerSickle() {
  Cell* player_cell = getCreatures()->getPlayer()->getCell(); // get player's cell
  Direction player_dir = getCreatures()->getPlayer()->getDirection(); // get player's direction
  Cell* in_front_of_player = getMaps()->getCellInFrontOff(player_cell, // get in front of cell
          player_dir);
  if (!getCreatures()->getPlants()->containsPlantAt(in_front_of_player)) {
    // no plant present
    view_.emptySickle();
  } else {
    Plant* plant = getCreatures()->getPlants()->getPlantAt(in_front_of_player);
    view_.plantRemoved();
    getCreatures()->getPlants()->removePlant(plant);
  }
  // advance game time
  advanceTime();
}

void Game::playerTeleport(std::string nama_area_tujuan) {
  GridMap* player_map = getCreatures()->getPlayer()->getMap();
  if (nama_area_tujuan == "house") {
    if (player_map != getMaps()->getField()) {
      view_.invalidTeleportDestination();
      return;
    } else {
      getCreatures()->getPlayer()->setLocation(
              getMaps()->getHouse()->getCellAt(
                  HouseConstants::getTeleportXFromField(),
                  HouseConstants::getTeleportYFromField()));
      view_.teleportSuccess(nama_area_tujuan);
    }
  } else if (nama_area_tujuan == "shop") {
    if (player_map != getMaps()->getField()) {
      view_.invalidTeleportDestination();
      return;
    } else {
      getCreatures()->getPlayer()->setLocation(
              getMaps()->getShop()->getCellAt(
                  ShopConstants::getTeleportXFromField(),
                  ShopConstants::getTeleportYFromField()));
      view_.teleportSuccess(nama_area_tujuan);
    }
  } else if (nama_area_tujuan == "field") {
    if (player_map == getMaps()->getField()) {
      view_.invalidTeleportDestination();
      return;
    } else {
      int x,y;
      if (player_map == getMaps()->getShop()) {
        x = FieldConstants::getTeleportXFromShop();
        y = FieldConstants::getTeleportYFromShop();
      } else if (player_map == getMaps()->getHouse()) {
        x = FieldConstants::getTeleportXFromHouse();
        y = FieldConstants::getTeleportYFromHouse();
      } else {
        assert(false);
      }
      getCreatures()->getPlayer()->setLocation(
              getMaps()->getField()->getCellAt(x,y));
      view_.teleportSuccess(nama_area_tujuan);
    }
  } else {
    view_.invalidTeleportDestination();
    return;
  }
  advanceTime();
  return;
}

void Game::playerShowTeleportHelp() {
  GridMap* player_map = getCreatures()->getPlayer()->getMap();
  std::vector<std::string> destinations;
  if (player_map == getMaps()->getField()) {
    destinations.push_back("house");
    destinations.push_back("shop");
  } else {
    destinations.push_back("field");
  }
  view_.showTeleportHelp(destinations);
  return;
}

void Game::playerWakeSpriteUp(int id) {
    if (id <= 0 || id > getCreatures()->getSprites()->getSpritesCount()) {
    view_.invalidSpriteId();
    return;
  }
  getCreatures()->getSprites()->getSpriteOfIndex(id)->wake();
  view_.wakeSprite(id);
  advanceTime();
}

void Game::playerWater() { // irv copas amy
  Cell* player_cell = getCreatures()->getPlayer()->getCell(); // get player's cell
  Direction player_dir = getCreatures()->getPlayer()->getDirection(); // get player's direction
  Cell* in_front_of_player = getMaps()->getCellInFrontOff(player_cell, // get in front of cell
          player_dir);
  if (!in_front_of_player->isPlantable()) {
    // not plantable
    view_.waterUnplantableCell(); //irv
  } else {
    PlantableCell* target = (PlantableCell* )(in_front_of_player);
    target->water(); // irv
    view_.waterSuccess();
  }

  // advance game time
  advanceTime();
  return;
}

void Game::playerSetSpriteToSleep(int id) {
  if (id <= 0 || id > getCreatures()->getSprites()->getSpritesCount()) {
    view_.invalidSpriteId();
    return;
  }
  getCreatures()->getSprites()->getSpriteOfIndex(id)->sleep();
  view_.sleepSprite(id);
  advanceTime();
}

void Game::start() {
  
  advanceDay();
  updateGraphics();
  
  view_.gameWelcome(username_);
  while (!exit_called_) {
    // parser_->getKeyPress();
    parser_.getKeyPress();
    updateGraphics();
  }
}

void Game::playerExitGame() { //ir
  GridMap* location = getCreatures()->getPlayer()->getMap();
  if (location != getMaps()->getHouse()) {
    // wrong location
    view_.needHouseArea();
    return;
  }
  exit_called_ = true;
  view_.exitGame();
}

void Game::playerShowSpriteStatus() {
  view_.printStatusKurcaci();
}

void Game::advanceDay() { //ir
  getCreatures()->advanceDay();
  getMaps()->advanceDay();

  int curseason = getGameTime()->getSeason();
  getGameTime()->increaseDay();
  if (curseason != getGameTime()->getSeason()) {
    view_.newSeason(Season::getSeasonName(getGameTime()->getSeason()),
            Season::getSeasonDescription(getGameTime()->getSeason()),
            getGameTime()->getSeasonLength());
  } else {
    view_.showSeason(Season::getSeasonName(getGameTime()->getSeason()),
            getGameTime()->getSeasonLength());
  }

  view_.dayAdvanced();
  disaster_.advanceDay();
}

void Game::advanceTime() { //ir
  getCreatures()->advanceTime();
  getMaps()->advanceTime();
  disaster_.advanceTime();
  getGameTime()->increaseTime();
  if (getGameTime()->getDayTime() >= GameConstants::getMaximumActionsPerDay()) {
    // actions count exceed limit
    advanceDay();
  }
}

void Game::updateGraphics() { //ir
  view_.drawPlayerContainingMap();
  view_.timeAdvanced();
}

void Game::playerShowInventory() { //ir
  view_.printInventory();
  view_.showMoney();
}

void Game::playerSleep() {
  GridMap* location = getCreatures()->getPlayer()->getMap();
  if (location != getMaps()->getHouse()) {
    // wrong location
    view_.needHouseArea();
    return;
  }
  int day = getGameTime()->getDayCount();
  while (day == getGameTime()->getDayCount()) {
    advanceTime();
  }
  return;
}

void Game::playerHelp() {
  view_.showHelp();
}

void Game::playerSuperAction() {
  Cell* player_cell = getCreatures()->getPlayer()->getCell(); // get player's cell
  Direction player_dir = getCreatures()->getPlayer()->getDirection(); // get player's direction
  Cell* in_front_of_player = getMaps()->getCellInFrontOff(player_cell, // get in front of cell
          player_dir);

  if (getCreatures()->getDropBoxes()->containsDropBoxAt(in_front_of_player)) {
    for (int i = 1; i <= getInventory()->getFilledSlotCount(); ++i) {
      if (getInventory()->getItemAt(i)->getType() == kFruit) {
        playerPut(i, getInventory()->numberOfItemsAtSlot(i));
        return;
      }
    }
  }

  if (in_front_of_player->isPlantable()) {
    PlantableCell* pc = (PlantableCell*) in_front_of_player;
    if (getCreatures()->getPlants()->containsPlantAt(pc)) {
      Plant* p = getCreatures()->getPlants()->getPlantAt(pc);
      if (p->isDead()) {
        playerSickle();
        return;
      }
      if (p->isHarvestable()) {
        playerHarvest();
        return;
      }
      if (!pc->isWatered()) {
        playerWater();
        return;
      }
    } else {
      if (pc->isBurning()) {
        playerWater();
        return;
      } else
      if (!pc->isPlowed()) {
        playerPlow();
        return;
      }
      for (int i = 1; i <= getInventory()->getFilledSlotCount(); ++i) {
        if (getInventory()->getItemAt(i)->getType() == kSeed) {
          // try to plant seed if the season match
          std::vector<bool> seasons = Season::getPossibleSeasons(
              PlantConstants::getPlantSeasonMask(
              SeedConstants::getPlantIdFromSeed(getInventory()->getItemAt(i)->getId())));
          if (seasons[getGameTime()->getSeason()]) {
            // season matches
            playerPut(i, 1);
            return;
          }
        }
      }
      if (!pc->isWatered()) {
        playerWater();
        return;
      }
    }
  }

  if (player_cell->getContainingMap() == getMaps()->getShop()) {
    for (int i = 1; i <= getInventory()->getFilledSlotCount(); ++i) {
      if (getInventory()->getItemAt(i)->getType() == kFruit) {
        playerSell(i, getInventory()->numberOfItemsAtSlot(i));
        return;
      }
    }
  }

  view_.cannotDetermineAppropriateFunction();

}

